#!/usr/local/bin/perl -w
use strict;
#sub POE::Kernel::ASSERT_DEFAULT () {1}
#sub POE::Kernel::TRACE_DEFAULT  () {1}
use FindBin;
use lib "$FindBin::Bin/../POE-Component-Client-DAAP/lib";
use lib "$FindBin::Bin/../POE-Component-Rendezvous/lib";

package Net::DAAP::Proxy;
use POE qw(Component::Client::DAAP Component::Rendezvous);
use POE::Component::Server::HTTP; # RC_*
use POE::Kernel;

use base qw(Net::DAAP::Server);
use Net::Rendezvous;

__PACKAGE__->mk_accessors(qw(daap));

our $id_offset = 1_000_000;

# captured from Server process
my @want_fields = qw(
dmap.itemkind
dmap.itemid
dmap.itemname
dmap.persistentid
com.apple.itunes.smart-playlist
dmap.haschildcontainers
dmap.parentcontainerid
daap.songdateadded
daap.songdatemodified
daap.songdisccount
daap.songdiscnumber
daap.songdisabled
daap.songeqpreset
daap.songformat
daap.songgenre
daap.songdescription
daap.songrelativevolume
daap.songsamplerate
daap.songsize
daap.songstarttime
daap.songstoptime
daap.songtime
daap.songtrackcount
daap.songtracknumber
daap.songuserrating
daap.songyear
daap.songdatakind
daap.songdataurl
daap.songcontentrating
daap.songartist
daap.songalbum
daap.songcomment
com.apple.itunes.norm-volume
com.apple.itunes.itms-songid
com.apple.itunes.itms-artistid
com.apple.itunes.itms-playlistid
com.apple.itunes.itms-composerid
com.apple.itunes.itms-genreid
com.apple.itunes.itms-storefrontid
);

use Data::Dumper;
use Sys::Hostname;

# copied from Net::DMAP::Server and nuked Rendezvous
sub new {
    my $class = shift;
    my $self = bless {
        db_uuid          => '13950142391337751523',
        revision        => 42,
        tracks          => {},
        playlists       => {},
        waiting_clients => [],
        poll_interval   => 20,
        @_ }, $class;
    $self->name( ref($self) ." " . hostname . " $$" ) unless $self->name;
    $self->port( $self->default_port ) unless $self->port;
    $self->find_tracks;
    $self->httpd( POE::Component::Server::HTTP->new(
        Port => $self->port,
        ContentHandler => { '/' => sub { $self->_handler(@_) } },
        StreamHandler => sub { $self->_stream_handler(@_) },
       ) );

    POE::Session->create(
        inline_states => {
            _start       => sub {
                $_[KERNEL]->alarm( poll_changed => time + $self->poll_interval )
;
            },
            poll_changed => sub {
                $self->poll_changed;
                $_[KERNEL]->yield('_start');
            },
        });

    return $self;
}

sub _stream_handler { }

sub find_tracks {
    my $self = shift;

    POE::Session->create(
        inline_states => {
            _start     => \&daap_start,
            connected  => \&daap_connected,
            dbs        => \&daap_dbs,
            songs      => \&daap_songs,
            got_data   => \&daap_got_data,
            test       => \&test,
            add_track  => sub { $self->add_track(@_[ARG0..$#_]) },

            published  => \&published,
            discovered => \&discovered,
            entries    => \&entries,
        },
        heap => {
            self => $self,
        },
    );
}

sub daap_start {
    $_[KERNEL]->alias_set('foo');
    warn "our session is ".$_[SESSION]->ID."\n";

    $_[HEAP]->{bonjour} = POE::Component::Rendezvous->new(
        debug => 1,
        rendezvous_options => [
            "daap"
        ],
        alias => 'bonjour',
    );

    $_[HEAP]->{publish} = POE::Component::Rendezvous->new(
        debug => 1,
        alias => 'publish',
        package => 'Net::Rendezvous::Publish',
        rendezvous_options => [ ],
    );

    $_[KERNEL]->yield('test');
    $_[HEAP]->{bonjour}->yield(discover => { event => 'discovered' });
}

sub discovered {
    warn "discovered\n";
    $_[HEAP]->{bonjour}->entries({ event => 'entries', wantarray => 1 });
    $_[HEAP]->{publish}->publish({ event => "published" },
        name => $_[HEAP]->{self}->name,
        type => '_daap._tcp',
        port => $_[HEAP]->{self}->port,
        txt  => "Database ID=".$_[HEAP]->{self}->db_uuid."\x{1}Machine Name=".$_[HEAP]->{self}->name,
    );
}

sub published {
    warn "published\n";
}

sub entries {
#    warn "entries: ".Data::Dumper->Dump([$_[ARG0]->{result}])."\n";
    unless ($_[ARG0]->{result}) {
        warn "no itunes servers found\n";
        return;
    }

    for my $share ( @{$_[ARG0]->{result}} ) {
        my $name;
        if ($share->name) {
            $name = $share->name;
            $name =~ s/\\(\d{3})/chr($1)/eg; # DNS escaped text
            #        $self->name($name . " (DAAP Proxy)");
        }
        $_[HEAP]->{host} = $share->address;
        $_[HEAP]->{port} = $share->port;
        $_[HEAP]->{name} = $name;

        my $alias = join ":", "server", $share->address, $share->port;
        my $daap = POE::Component::Client::DAAP->new(
            debug => 1,
            daap_options => [
                SERVER_HOST => $_[HEAP]->{host},
                SERVER_PORT => $_[HEAP]->{port},
                #            PASSWORD => $password
                SONG_ATTRIBUTES => \@want_fields,
            ],
            alias => $alias,
        );

        my $session_id = $daap->session_id();
        $_[HEAP]->{"server_$session_id"} = $daap;

        $_[KERNEL]->post($session_id, connect => { event => 'connected', session_id => $session_id });

        warn "finding tracks from $name\n";
    }
}

sub test {
    warn ".\n";
    $_[KERNEL]->delay_set(test => 1);
}

sub daap_connected {
    unless ($_[ARG0]->{result}) {
        warn "connect failed\n";
        $_[KERNEL]->post($_[ARG0]->{session_id}, 'shutdown');
        return;
    }

    $_[KERNEL]->post($_[ARG0]->{session_id}, databases => { event => 'dbs', session_id => $_[ARG0]->{session_id} });
    warn "getting databases\n";
}

sub daap_dbs {
    warn "databases: ".Data::Dumper->Dump([$_[ARG0]->{result}])."\n";
    $_[KERNEL]->post($_[ARG0]->{session_id}, songs => { event => 'songs', session_id => $_[ARG0]->{session_id} });
    warn "getting songs\n";
}

sub daap_songs {
#    warn "songs: ".Data::Dumper->Dump([$_[ARG0]->{result}]) . "\n";

    for my $id (keys %{$_[ARG0]->{result}}) {
        my $new_id = $id + $id_offset * $_[ARG0]->{session_id};
        warn "adding $id from $_[ARG0]->{session_id} as $new_id\n";
        $_[KERNEL]->yield(add_track => $new_id => $_[ARG0]->{result}->{$id});
    }
}

sub daap_got_data {
    my $data = $_[ARG0]->{result};
    warn "data size ", length($data), " bytes\n";
    $_[ARG0]->{response}->send($data);
    $_[ARG0]->{response}->close;
    $_[ARG0]->{response}->header('Connection' => 'close');
}

sub _stream_handler { }

sub add_track {
    my($self, $id, $data) = @_;
    my $track = Net::DAAP::Proxy::Track->new_from_daap( $id, $data ) or next;
    if ($self->tracks->{$id}){
        warn "$id dup!\n";
    }
    $self->tracks->{ $id } = $track;
}

sub database_item {
    my($self, $request, $response) = @_;
    my($session_id, $id);
    {
        use integer;
        $session_id = $1 / $id_offset;
        $id = $1 % $id_offset;
    }
    $response->streaming(1);
    warn "getting $id from $session_id\n";
    $poe_kernel->post($session_id => 'get' => {
        event    => 'got_data',
        session  => 'foo',
        response => $response,
    }, $id);
}

package Net::DAAP::Proxy::Track;
use base qw( Net::DAAP::Server::Track );

__PACKAGE__->mk_accessors(qw(daap_songcontentrating com_apple_itunes_itms_storefrontid));

sub new_from_daap {
    my($class, $id, $song) = @_;

    my $self = $class->SUPER::new;
    for my $field (keys %$song) {
        next if $field eq 'dmap.itemid';
        (my $method = $field) =~  s{[.-]}{_}g;
        $self->$method($song->{$field}) if $self->can($method);
        warn "$method" unless $self->can($method);
    }

    $self->dmap_itemid($id);
    $self;
}

package main;
use POE;
my $server = Net::DAAP::Proxy->new(
    port => 6666,
    path => "/tmp",
);
$server->debug(1);

$poe_kernel->run;


